require(['jquery', 'esprima', 'highcharts', 'highcharts-more', 'jslint', 'jshint'], function($, esprima, Highcharts){
	
	var totalMinutesSpent;
	
	var escomplexRules = {
		complexity : {
			cost : 90,
			limit : 7,
			index : 'testability',
			message : 'Цикломатическая сложность >',
			enabled : true
		},
		parameters : {
			cost : 30,
			index : 'testability',
			limit : 3,
			message : 'Количество параметров функции >',
			enabled : true
		},
		sloc : {
			cost : 30,
			limit : 25,
			index : 'testability',
			message : 'Число логических строк функции >',
			enabled : true
		}
	};
	var hintRules = {
		equal : {
			cost : 15,
			index: 'security',
			code : 'W116',
			message : 'Использование нестрогого сравнения',
			enabled : true
		},
		useToCompare : {
			cost : 10,
			index : 'security',
			code : 'W041',
			message : 'Нарушение правил сравнения',
			enabled : true
		},
		eval : {
			cost : 60,
			index: 'security',
			code : 'W061',
			message : 'Использование eval',
			enabled : true
		},
		args : {
			reason : "Expected an identifier and instead saw 'arguments' (a reserved word).",
			cost : 15,
			index : 'reliability',
			message : 'Использование args',
			enabled : true
		},
		forInFilter : {
			code : "W089",
			cost : 10,
			index: 'reliability',
			message : 'Использование for in без фильтрации свойств',
			enabled : true
		},
		withWord : {
			reason : "Don't use 'with'.",
			cost : 10,
			index: 'reliability',
			message : 'Использование with',
			enabled : true
		},
		curlyBraces : {
			reason : "Expected '{' and instead saw",
			cost : 10,
			index: 'changeability',
			message : 'Использование условий без фигурных скобок',
			enabled : true
		},
		switchBreaks : {
			reason : "Expected a 'break' statement before 'case'.",
			cost : 20,
			index: 'reliability',
			message : 'Пропуск "break" в конструкции switch',
			enabled : true
		},
		subExpressAssign : {
			raw : "Expected a conditional expression and instead saw an assignment.",
			cost : 10,
			index: 'maintainability',
			message: 'Использование присваивания в подвыражении ',
			enabled : true
		},
		funcInLoop : {
			code : "W082",
			cost : 10,
			index: 'efficiency',
			message: 'Объявление функции в цикле',
			enabled : true
		},
		emptyBlock : {
			code : "W035",
			cost : 10,
			index: 'changeability',
			message: 'Пустой блок',
			enabled : true
		},
		usageBeforeDeclare : {
			code : "W003",
			cost : 10,
			index: 'reliability',
			message: 'Использование переменной до объявления',
			enabled : true
		},
		notDefined : {
			code : "W117",
			cost : 15,
			index : 'reliability',
			message : 'Использование необъявленной переменной',
			enabled : true
		},
		arrayUsage : {
			code : "W009",
			cost : 10,
			index: 'maintainability',
			message : 'Использование конструкции Array',
			enabled : true
		},
		bitwise : {
			code : "W016",
			cost : 10,
			index: 'maintainability',
			message: 'Использование побитовых операторов',
			enabled : true
		},
		missingRadix : {
			reason : "Missing radix parameter.",
			cost : 30,
			index: 'changeability',
			message : 'Отсутствует параметр разрядности',
			enabled : true
		},
		wrapperObjectUsage : {
			code : "W053",
			cost : 10,
			index: 'changeability',
			message : 'Использование объектов-оберток',
			enabled : true
		},
		semicolons : {
			code : "W033",
			cost : 5,
			index: 'security',
			message : 'Пропуск ";"',
			enabled : true
		},
		unreachableCode : {
			code : "W027",
			cost : 20,
			index: 'efficiency',
			message : 'Недостижимый код',
			enabled : true
		},
		unusedVariable : {
			code : "W098",
			cost : 5,
			index: 'efficiency',
			message : 'Неиспользуемая переменная',
			enabled : true
		},
		monkeyPatching : {
			code : "W121",
			cost : 10,
			index : 'maintainability',
			message : 'Расширение стандартного конструктора',
			enabled : true
		},
		deepNesting : {
			code : "W073",
			cost : 20,
			index : 'changeability',
			message : 'Глубокая вложенность условий',
			enabled : true
		},
		outOfScope : {
			code : "W038",
			cost : 20,
			index : 'reliability',
			message : 'Использование переменной в неверной области видимости',
			enabled : true
		},
		weirdConstruction : {
			code : "W057",
			cost : 30,
			index : 'changeability',
			message : 'Неправильное использование оператора "new"',
			enabled : true
		},
		reassignment : {
			code : "W004",
			cost : 20,
			index : 'reliability',
			message : 'Переопределение переменной',
			enabled : true
		},
		codeAlignment : {
			code : "W030",
			cost : 10,
			index : 'maintainability',
			message : 'Неверное форматирование кода',
			enabled : true
		},
		minorSyntaxViolations : {
			cost : 10,
			index : 'maintainability',
			message : 'Нарушение code conventions',
			enabled : true
		}
	};
	
	var hintOptions = {
		bitwise : true,
		indent : true,
		camelcase : true,
		curly : true,
		latedef : true,
		eqeqeq : true,
		forin : true,
		freeze : true,
		newcap : true,
		noarg : true,
		noempty : true,
		undef : true,
		unused : true,
		trailing : true,
		maxdepth : 2,
		white : true,
		evil : false,
		maxerr : 10000,
		sub : true,
		predef : ["$", "require", "console", "alert", "prompt"],
		browser : true,
		boss : false,
		eqnull : false,
		lastsemic : false,
		proto : false,
		shadow : false,
		funcscope : false,
		devel : true
	};
	
	function getLintViolations() {		
		var code = $('#code').val();
		JSHINT(code, hintOptions);
		
		var errors = JSHINT.errors;
		if(errors[errors.length-1] === null) {
			throw new Error('Code is not valid. See errors.');
		}
		
		var results = [],
			rule,
			i;
		errors.forEach(function(error) {
			var found;
			for(i in hintRules) {
				if(hintRules.hasOwnProperty(i)) {
					rule = hintRules[i];
					if( (rule.raw && error.raw.indexOf(rule.raw) !== -1) ||
							(rule.code && error.code.indexOf(rule.code) !== -1) || 
							(rule.reason && error.reason.indexOf(rule.reason) !== -1) ) {
							if(rule.enabled){
								results.push(rule);
							}
							found = true;								
					}
				}
			}
			if(!found){
				console.log(error);
				results.push(hintRules.minorSyntaxViolations);
			}
		});
		return results;
	}
	
	function getEscomplexData(callback){
		var ast;
		ast = esprima.parse($('#code').val());
		$.ajax({
			type : 'POST',
			url : '/analyze',
			data : {
				json : JSON.stringify(ast)
			},
			success : function(data){
				callback(data);
			}
		});
	}
	
	function getEscomplexViolations(escomplexData) {
			var result = [];
			var functions = escomplexData.functions;
			functions.forEach(function(func) {
				if(escomplexRules.complexity.enabled && (func.cyclomatic > escomplexRules.complexity.limit)) {
					result.push(escomplexRules.complexity);
				}
				if(escomplexRules.parameters.enabled && (func.params > escomplexRules.parameters.limit)) {
					result.push(escomplexRules.parameters);
				}
				if(escomplexRules.sloc.enabled && (func.sloc.logical > escomplexRules.sloc.limit)) {
					result.push(escomplexRules.sloc);
				}
			});
			return result;
	}
	
	function calculateIndices(violations) {
		var result = {
			maintainability : 0,
			security : 0,
			efficiency : 0,
			changeability : 0,
			reliability : 0,
			testability : 0,
			sum : 0
		},
			indexRules,
			rule,
			index;

		violations.forEach(function(violation){
			result[violation.index] = result[violation.index] + violation.cost;
		});
		
		var sum = 0;
		for(var i in result){
			if(result.hasOwnProperty(i)){
				result[i] = +(result[i]/totalMinutesSpent).toFixed(3);
				sum += result[i];
			}
		}
		result.sum = +sum.toFixed(3);
		
		return result;
	}
	
	function calculateDebt(violations){
		var debt = 0;
		violations.forEach(function(violation){
			debt += violation.cost;
		});
		return +(debt/60).toFixed(1);
	}
	
	function buildBarChart(results){
        $('#barchart').highcharts({
            chart: {
                type: 'bar'
            },
            title : {
            	text : 'Technical debt'
            },
            xAxis: {
                categories: ['Maintainability', 'Security', 'Efficiency', 'Changeability', 'Reliability', 'Testability'],
                title: {
                    text: null
                }
            },
            yAxis: {
                min: 0,
                title: {
                    align: 'high'
                },
                labels: {
                    overflow: 'justify'
                }
            },
            legend: {
                layout: 'vertical',
                align: 'right',
                verticalAlign: 'top',
                x: -40,
                y: 100,
                floating: true,
                borderWidth: 1,
                backgroundColor: (Highcharts.theme && Highcharts.theme.legendBackgroundColor || '#FFFFFF'),
                shadow: true
            },
            series: [{
                name: 'Technical debts',
                data: [results.maintainability, results.security, results.efficiency, 
                       results.changeability, results.reliability, results.testability]
            }]
        });
	}
	
	function buildWebChart(results){
		$('#webchart').highcharts({
		    chart: {
		        polar: true,
		        type: 'line'
		    },
		    title: {
		        text: 'Actual vs planned',
		        x: -80
		    },
		    pane: {
		    	size: '80%'
		    },
		    xAxis: {
		        categories: ['Maintainability', 'Security', 'Efficiency', 'Changeability', 'Reliability', 'Testability'],
		        tickmarkPlacement: 'on',
		        lineWidth: 0
		    },
		    yAxis: {
		        gridLineInterpolation: 'polygon',
		        lineWidth: 0,
		        min: 0
		    },
		    tooltip: {
		    	shared: true,
		    },
		    legend: {
		        align: 'right',
		        verticalAlign: 'top',
		        y: 70,
		        layout: 'vertical'
		    },
		    series: [{
		        name: 'Actual',
		        data: [results.maintainability, results.security, results.efficiency, 
                       results.changeability, results.reliability, results.testability],
		        pointPlacement: 'on'
		    }, {
		        name: 'Planned',
		        data: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2],
		        pointPlacement: 'on'
		    }]
		
		});
	}
	
	function updateResults(results){
		$('.results').show();
		var i;
		for(i in results){
			if (results.hasOwnProperty(i)){
				$('#'+i).html(results[i]);
			}
		}
		buildBarChart(results);
		buildWebChart(results);
	}
	
	function buildSettings(){
		for(var i in escomplexRules){
			if(escomplexRules.hasOwnProperty(i)){
				var rule = escomplexRules[i];
				$('#settingsList').append('<li class="list-group-item">'+
												'<input type="checkbox" checked="true" id="'+i+'Enabled"/>'+
												'<span>'+rule.message+'</span>'+
												'<input type="text" id="'+i+'Limit" value="'+rule.limit+'"  />'+
												' - '+
												'<input type="text" id="'+i+'Cost" value="'+rule.cost+'" />'+
												'<span>минут</span>'+
											'</li>');
			}
		}
		for(var i in hintRules){
			if(hintRules.hasOwnProperty(i)){
				var rule = hintRules[i];
				$('#settingsList').append('<li class="list-group-item">'+
												'<input type="checkbox" checked="true" id="'+i+'Enabled" />'+
												'<span>'+rule.message+' - </span>'+
												'<input type="text" id="'+i+'Cost" value="'+rule.cost+'" />'+
												'<span>минут</span>'+
											'</li>');
			}
		}
	}
	
	function buildHintRules(){
		try{
			for(var i in hintRules){
				if(hintRules.hasOwnProperty(i)){
					hintRules[i].cost = parseInt($('#'+i+'Cost').val());
					hintRules[i].enabled = $('#'+i+'Enabled')[0].checked;
				}
			}
		} catch(e) {
			alert('Для задания параметров анализа можно использовать только числа');
		}
	}
	
	function buildEscomplexRules(){
		try{
			for(var i in escomplexRules){
				if(escomplexRules.hasOwnProperty(i)){
					escomplexRules[i].cost = parseInt($('#'+i+'Cost').val());
					escomplexRules[i].limit = parseInt($('#'+i+'Limit').val());
					escomplexRules[i].enabled = $('#'+i+'Enabled')[0].checked;
				}
			}
		} catch(e) {
			alert('Для задания параметров анализа можно использовать только числа');
		}
	}

	$(function(){
		
		buildSettings();
		
		$('#submit').on('click', function(event){
			
			buildHintRules();
			buildEscomplexRules();
			
			try {
				getEscomplexData(function(data){			
					var violations = [];
					
					totalMinutesSpent = data.aggregate.halstead.effort/1080;
					var totalTime = +(totalMinutesSpent/60).toFixed(1);

					var lintViolations = getLintViolations();
					var escomplexViolations = getEscomplexViolations(data);
					
					console.log('lint', lintViolations);
					console.log('escomplex', escomplexViolations);
					
					[].push.apply(violations, lintViolations);
					[].push.apply(violations, escomplexViolations);
					
					var results = calculateIndices(violations, data.aggregate.halstead);
					
					results.debt = calculateDebt(violations);
					results.totalTime = totalTime;
					
					updateResults(results);
				});
				
			}catch(err) {
				alert(err.message);
			}
		});
		
		/* =========== Tabs control =============*/
		var _tabFn = function(e){
			e.preventDefault();
			$('.tab').hide();
			$('.active').toggleClass('active');
			
			var target = e.currentTarget;
			$(target).parent().toggleClass('active');
			
			$('#' + target.id + 'Tab').show();
		};
		$('.nav li a').on('click', _tabFn);
		
		$('.tab.active').show();
		
	});
});